import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import {
  CallToolResultSchema,
  type CallToolRequest,
} from '@modelcontextprotocol/sdk/types.js';
import { StreamTransport } from '@supabase/mcp-utils';
import { codeBlock, stripIndent } from 'common-tags';
import gqlmin from 'gqlmin';
import { setupServer } from 'msw/node';
import { beforeEach, describe, expect, test } from 'vitest';
import {
  ACCESS_TOKEN,
  API_URL,
  contentApiMockSchema,
  mockContentApiSchemaLoadCount,
  createOrganization,
  createProject,
  createBranch,
  MCP_CLIENT_NAME,
  MCP_CLIENT_VERSION,
  mockBranches,
  mockContentApi,
  mockManagementApi,
  mockOrgs,
  mockProjects,
} from '../test/mocks.js';
import { createSupabaseApiPlatform } from './platform/api-platform.js';
import { BRANCH_COST_HOURLY, PROJECT_COST_MONTHLY } from './pricing.js';
import { createSupabaseMcpServer } from './server.js';
import type { SupabasePlatform } from './platform/types.js';

beforeEach(async () => {
  mockOrgs.clear();
  mockProjects.clear();
  mockBranches.clear();
  mockContentApiSchemaLoadCount.value = 0;

  const server = setupServer(...mockContentApi, ...mockManagementApi);
  server.listen({ onUnhandledRequest: 'error' });
});

type SetupOptions = {
  accessToken?: string;
  projectId?: string;
  platform?: SupabasePlatform;
  readOnly?: boolean;
  features?: string[];
};

/**
 * Sets up an MCP client and server for testing.
 */
async function setup(options: SetupOptions = {}) {
  const { accessToken = ACCESS_TOKEN, projectId, readOnly, features } = options;
  const clientTransport = new StreamTransport();
  const serverTransport = new StreamTransport();

  clientTransport.readable.pipeTo(serverTransport.writable);
  serverTransport.readable.pipeTo(clientTransport.writable);

  const client = new Client(
    {
      name: MCP_CLIENT_NAME,
      version: MCP_CLIENT_VERSION,
    },
    {
      capabilities: {},
    }
  );

  const platform =
    options.platform ??
    createSupabaseApiPlatform({
      accessToken,
      apiUrl: API_URL,
    });

  const server = createSupabaseMcpServer({
    platform,
    projectId,
    readOnly,
    features,
  });

  await server.connect(serverTransport);
  await client.connect(clientTransport);

  /**
   * Calls a tool with the given parameters.
   *
   * Wrapper around the `client.callTool` method to handle the response and errors.
   */
  async function callTool(params: CallToolRequest['params']) {
    const output = await client.callTool(params);
    const { content } = CallToolResultSchema.parse(output);
    const [textContent] = content;

    if (!textContent) {
      return undefined;
    }

    if (textContent.type !== 'text') {
      throw new Error('tool result content is not text');
    }

    if (textContent.text === '') {
      throw new Error('tool result content is empty');
    }

    const result = JSON.parse(textContent.text);

    if (output.isError) {
      throw new Error(result.error.message);
    }

    return result;
  }

  return { client, clientTransport, callTool, server, serverTransport };
}

describe('tools', () => {
  test('list organizations', async () => {
    const { callTool } = await setup();

    const org1 = await createOrganization({
      name: 'Org 1',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });
    const org2 = await createOrganization({
      name: 'Org 2',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const result = await callTool({
      name: 'list_organizations',
      arguments: {},
    });

    expect(result).toEqual([
      { id: org1.id, name: org1.name },
      { id: org2.id, name: org2.name },
    ]);
  });

  test('get organization', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const result = await callTool({
      name: 'get_organization',
      arguments: {
        id: org.id,
      },
    });

    expect(result).toEqual(org);
  });

  test('get next project cost for free org', async () => {
    const { callTool } = await setup();

    const freeOrg = await createOrganization({
      name: 'Free Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const result = await callTool({
      name: 'get_cost',
      arguments: {
        type: 'project',
        organization_id: freeOrg.id,
      },
    });

    expect(result).toEqual(
      'The new project will cost $0 monthly. You must repeat this to the user and confirm their understanding.'
    );
  });

  test('get next project cost for paid org with 0 projects', async () => {
    const { callTool } = await setup();

    const paidOrg = await createOrganization({
      name: 'Paid Org',
      plan: 'pro',
      allowed_release_channels: ['ga'],
    });

    const result = await callTool({
      name: 'get_cost',
      arguments: {
        type: 'project',
        organization_id: paidOrg.id,
      },
    });

    expect(result).toEqual(
      'The new project will cost $0 monthly. You must repeat this to the user and confirm their understanding.'
    );
  });

  test('get next project cost for paid org with > 0 active projects', async () => {
    const { callTool } = await setup();

    const paidOrg = await createOrganization({
      name: 'Paid Org',
      plan: 'pro',
      allowed_release_channels: ['ga'],
    });

    const priorProject = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: paidOrg.id,
    });
    priorProject.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'get_cost',
      arguments: {
        type: 'project',
        organization_id: paidOrg.id,
      },
    });

    expect(result).toEqual(
      `The new project will cost $${PROJECT_COST_MONTHLY} monthly. You must repeat this to the user and confirm their understanding.`
    );
  });

  test('get next project cost for paid org with > 0 inactive projects', async () => {
    const { callTool } = await setup();

    const paidOrg = await createOrganization({
      name: 'Paid Org',
      plan: 'pro',
      allowed_release_channels: ['ga'],
    });

    const priorProject = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: paidOrg.id,
    });
    priorProject.status = 'INACTIVE';

    const result = await callTool({
      name: 'get_cost',
      arguments: {
        type: 'project',
        organization_id: paidOrg.id,
      },
    });

    expect(result).toEqual(
      `The new project will cost $0 monthly. You must repeat this to the user and confirm their understanding.`
    );
  });

  test('get branch cost', async () => {
    const { callTool } = await setup();

    const paidOrg = await createOrganization({
      name: 'Paid Org',
      plan: 'pro',
      allowed_release_channels: ['ga'],
    });

    const result = await callTool({
      name: 'get_cost',
      arguments: {
        type: 'branch',
        organization_id: paidOrg.id,
      },
    });

    expect(result).toEqual(
      `The new branch will cost $${BRANCH_COST_HOURLY} hourly. You must repeat this to the user and confirm their understanding.`
    );
  });

  test('list projects', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project1 = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const project2 = await createProject({
      name: 'Project 2',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const result = await callTool({
      name: 'list_projects',
      arguments: {},
    });

    expect(result).toEqual([project1.details, project2.details]);
  });

  test('get project', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const result = await callTool({
      name: 'get_project',
      arguments: {
        id: project.id,
      },
    });

    expect(result).toEqual(project.details);
  });

  test('create project', async () => {
    const { callTool } = await setup();

    const freeOrg = await createOrganization({
      name: 'Free Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'project',
        recurrence: 'monthly',
        amount: 0,
      },
    });

    const newProject = {
      name: 'New Project',
      region: 'us-east-1',
      organization_id: freeOrg.id,
      confirm_cost_id,
    };

    const result = await callTool({
      name: 'create_project',
      arguments: newProject,
    });

    const { confirm_cost_id: _, ...projectInfo } = newProject;

    expect(result).toEqual({
      ...projectInfo,
      id: expect.stringMatching(/^.+$/),
      created_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
      status: 'UNKNOWN',
    });
  });

  test('create project in read-only mode throws an error', async () => {
    const { callTool } = await setup({ readOnly: true });

    const freeOrg = await createOrganization({
      name: 'Free Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'project',
        recurrence: 'monthly',
        amount: 0,
      },
    });

    const newProject = {
      name: 'New Project',
      region: 'us-east-1',
      organization_id: freeOrg.id,
      confirm_cost_id,
    };

    const result = callTool({
      name: 'create_project',
      arguments: newProject,
    });

    await expect(result).rejects.toThrow(
      'Cannot create a project in read-only mode.'
    );
  });

  test('create project without region fails', async () => {
    const { callTool } = await setup();

    const freeOrg = await createOrganization({
      name: 'Free Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'project',
        recurrence: 'monthly',
        amount: 0,
      },
    });

    const newProject = {
      name: 'New Project',
      organization_id: freeOrg.id,
      confirm_cost_id,
    };

    const createProjectPromise = callTool({
      name: 'create_project',
      arguments: newProject,
    });

    await expect(createProjectPromise).rejects.toThrow();
  });

  test('create project without cost confirmation fails', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'Paid Org',
      plan: 'pro',
      allowed_release_channels: ['ga'],
    });

    const newProject = {
      name: 'New Project',
      region: 'us-east-1',
      organization_id: org.id,
    };

    const createProjectPromise = callTool({
      name: 'create_project',
      arguments: newProject,
    });

    await expect(createProjectPromise).rejects.toThrow(
      'User must confirm understanding of costs before creating a project.'
    );
  });

  test('pause project', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    await callTool({
      name: 'pause_project',
      arguments: {
        project_id: project.id,
      },
    });

    expect(project.status).toEqual('INACTIVE');
  });

  test('pause project in read-only mode throws an error', async () => {
    const { callTool } = await setup({ readOnly: true });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = callTool({
      name: 'pause_project',
      arguments: {
        project_id: project.id,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot pause a project in read-only mode.'
    );
  });

  test('restore project', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'INACTIVE';

    await callTool({
      name: 'restore_project',
      arguments: {
        project_id: project.id,
      },
    });

    expect(project.status).toEqual('ACTIVE_HEALTHY');
  });

  test('restore project in read-only mode throws an error', async () => {
    const { callTool } = await setup({ readOnly: true });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'INACTIVE';

    const result = callTool({
      name: 'restore_project',
      arguments: {
        project_id: project.id,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot restore a project in read-only mode.'
    );
  });

  test('get project url', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'get_project_url',
      arguments: {
        project_id: project.id,
      },
    });
    expect(result).toEqual(`https://${project.id}.supabase.co`);
  });

  test('get anon or publishable keys', async () => {
    const { callTool } = await setup();
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });
    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'get_publishable_keys',
      arguments: {
        project_id: project.id,
      },
    });

    expect(result).toBeInstanceOf(Array);
    expect(result.length).toBe(2);

    // Check legacy anon key
    const anonKey = result.find((key: any) => key.name === 'anon');
    expect(anonKey).toBeDefined();
    expect(anonKey.api_key).toEqual('dummy-anon-key');
    expect(anonKey.type).toEqual('legacy');
    expect(anonKey.id).toEqual('anon-key-id');
    expect(anonKey.disabled).toBe(true);

    // Check publishable key
    const publishableKey = result.find(
      (key: any) => key.type === 'publishable'
    );
    expect(publishableKey).toBeDefined();
    expect(publishableKey.api_key).toEqual('sb_publishable_dummy_key_1');
    expect(publishableKey.type).toEqual('publishable');
    expect(publishableKey.description).toEqual('Main publishable key');
  });

  test('list storage buckets', async () => {
    const { callTool } = await setup({ features: ['storage'] });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    project.createStorageBucket('bucket1', true);
    project.createStorageBucket('bucket2', false);

    const result = await callTool({
      name: 'list_storage_buckets',
      arguments: {
        project_id: project.id,
      },
    });

    expect(Array.isArray(result)).toBe(true);
    expect(result.length).toBe(2);
    expect(result[0]).toEqual(
      expect.objectContaining({
        name: 'bucket1',
        public: true,
        created_at: expect.any(String),
        updated_at: expect.any(String),
      })
    );
    expect(result[1]).toEqual(
      expect.objectContaining({
        name: 'bucket2',
        public: false,
        created_at: expect.any(String),
        updated_at: expect.any(String),
      })
    );
  });

  test('get storage config', async () => {
    const { callTool } = await setup({ features: ['storage'] });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'get_storage_config',
      arguments: {
        project_id: project.id,
      },
    });

    expect(result).toEqual({
      fileSizeLimit: expect.any(Number),
      features: {
        imageTransformation: { enabled: expect.any(Boolean) },
        s3Protocol: { enabled: expect.any(Boolean) },
      },
    });
  });

  test('update storage config', async () => {
    const { callTool } = await setup({ features: ['storage'] });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const config = {
      fileSizeLimit: 50,
      features: {
        imageTransformation: { enabled: true },
        s3Protocol: { enabled: false },
      },
    };

    const result = await callTool({
      name: 'update_storage_config',
      arguments: {
        project_id: project.id,
        config,
      },
    });

    expect(result).toEqual({ success: true });
  });

  test('update storage config in read-only mode throws an error', async () => {
    const { callTool } = await setup({ readOnly: true, features: ['storage'] });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const config = {
      fileSizeLimit: 50,
      features: {
        imageTransformation: { enabled: true },
        s3Protocol: { enabled: false },
      },
    };

    const result = callTool({
      name: 'update_storage_config',
      arguments: {
        project_id: project.id,
        config,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot update storage config in read-only mode.'
    );
  });

  test('execute sql', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const query = 'select 1+1 as sum';

    const result = await callTool({
      name: 'execute_sql',
      arguments: {
        project_id: project.id,
        query,
      },
    });

    expect(result).toContain('untrusted user data');
    expect(result).toMatch(/<untrusted-data-\w{8}-\w{4}-\w{4}-\w{4}-\w{12}>/);
    expect(result).toContain(JSON.stringify([{ sum: 2 }]));
    expect(result).toMatch(/<\/untrusted-data-\w{8}-\w{4}-\w{4}-\w{4}-\w{12}>/);
  });

  test('can run read queries in read-only mode', async () => {
    const { callTool } = await setup({ readOnly: true });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const query = 'select 1+1 as sum';

    const result = await callTool({
      name: 'execute_sql',
      arguments: {
        project_id: project.id,
        query,
      },
    });

    expect(result).toContain('untrusted user data');
    expect(result).toMatch(/<untrusted-data-\w{8}-\w{4}-\w{4}-\w{4}-\w{12}>/);
    expect(result).toContain(JSON.stringify([{ sum: 2 }]));
    expect(result).toMatch(/<\/untrusted-data-\w{8}-\w{4}-\w{4}-\w{4}-\w{12}>/);
  });

  test('cannot run write queries in read-only mode', async () => {
    const { callTool } = await setup({ readOnly: true });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const query =
      'create table test (id integer generated always as identity primary key)';

    const resultPromise = callTool({
      name: 'execute_sql',
      arguments: {
        project_id: project.id,
        query,
      },
    });

    await expect(resultPromise).rejects.toThrow(
      'permission denied for schema public'
    );
  });

  test('apply migration, list migrations, check tables', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const name = 'test_migration';
    const query =
      'create table test (id integer generated always as identity primary key)';

    const result = await callTool({
      name: 'apply_migration',
      arguments: {
        project_id: project.id,
        name,
        query,
      },
    });

    expect(result).toEqual({ success: true });

    const listMigrationsResult = await callTool({
      name: 'list_migrations',
      arguments: {
        project_id: project.id,
      },
    });

    expect(listMigrationsResult).toEqual([
      {
        name,
        version: expect.stringMatching(/^\d{14}$/),
      },
    ]);

    const listTablesResult = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: project.id,
        schemas: ['public'],
      },
    });

    expect(listTablesResult).toEqual([
      {
        schema: 'public',
        name: 'test',
        rls_enabled: false,
        rows: 0,
        columns: [
          {
            name: 'id',
            data_type: 'integer',
            format: 'int4',
            options: ['identity', 'updatable'],
            identity_generation: 'ALWAYS',
          },
        ],
        primary_keys: ['id'],
      },
    ]);
  });

  test('cannot apply migration in read-only mode', async () => {
    const { callTool } = await setup({ readOnly: true });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const name = 'test-migration';
    const query =
      'create table test (id integer generated always as identity primary key)';

    const resultPromise = callTool({
      name: 'apply_migration',
      arguments: {
        project_id: project.id,
        name,
        query,
      },
    });

    await expect(resultPromise).rejects.toThrow(
      'Cannot apply migration in read-only mode.'
    );
  });

  test('list tables only under a specific schema', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    await project.db.exec('create schema test;');
    await project.db.exec(
      'create table public.test_1 (id serial primary key);'
    );
    await project.db.exec('create table test.test_2 (id serial primary key);');

    const result = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: project.id,
        schemas: ['test'],
      },
    });

    expect(result).toEqual(
      expect.arrayContaining([expect.objectContaining({ name: 'test_2' })])
    );
    expect(result).not.toEqual(
      expect.arrayContaining([expect.objectContaining({ name: 'test_1' })])
    );
  });

  test('listing all tables excludes system schemas', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: project.id,
      },
    });

    expect(result).not.toEqual(
      expect.arrayContaining([
        expect.objectContaining({ schema: 'pg_catalog' }),
      ])
    );

    expect(result).not.toEqual(
      expect.arrayContaining([
        expect.objectContaining({ schema: 'information_schema' }),
      ])
    );

    expect(result).not.toEqual(
      expect.arrayContaining([expect.objectContaining({ schema: 'pg_toast' })])
    );
  });

  test('list_tables is not vulnerable to SQL injection via schemas parameter', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'SQLi Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'SQLi Project',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    // Attempt SQL injection via schemas parameter using payload from HackerOne report
    // This payload attempts to break out of the string and inject a division by zero expression
    // Reference: https://linear.app/supabase/issue/AI-139
    const maliciousSchema = "public') OR (SELECT 1)=1/0--";

    // With proper parameterization, this should NOT throw "division by zero" error
    // The literal schema name doesn't exist, so it should return empty array
    // WITHOUT parameterization, this would throw: "division by zero" error
    const maliciousResult = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: project.id,
        schemas: [maliciousSchema],
      },
    });

    // Should return empty array without errors, proving the SQL injection was prevented
    expect(maliciousResult).toEqual([]);
  });

  test('list extensions', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'list_extensions',
      arguments: {
        project_id: project.id,
      },
    });

    expect(result).toMatchInlineSnapshot(`
      [
        {
          "comment": "PL/pgSQL procedural language",
          "default_version": "1.0",
          "installed_version": "1.0",
          "name": "plpgsql",
          "schema": "pg_catalog",
        },
      ]
    `);
  });

  test('invalid access token', async () => {
    const { callTool } = await setup({ accessToken: 'bad-token' });

    const listOrganizationsPromise = callTool({
      name: 'list_organizations',
      arguments: {},
    });

    await expect(listOrganizationsPromise).rejects.toThrow('Unauthorized.');
  });

  test('invalid sql for apply_migration', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const name = 'test-migration';
    const query = 'invalid sql';

    const applyMigrationPromise = callTool({
      name: 'apply_migration',
      arguments: {
        project_id: project.id,
        name,
        query,
      },
    });

    await expect(applyMigrationPromise).rejects.toThrow(
      'syntax error at or near "invalid"'
    );
  });

  test('invalid sql for execute_sql', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const query = 'invalid sql';

    const executeSqlPromise = callTool({
      name: 'execute_sql',
      arguments: {
        project_id: project.id,
        query,
      },
    });

    await expect(executeSqlPromise).rejects.toThrow(
      'syntax error at or near "invalid"'
    );
  });

  test('get logs for each service type', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const services = [
      'api',
      'branch-action',
      'postgres',
      'edge-function',
      'auth',
      'storage',
      'realtime',
    ] as const;

    for (const service of services) {
      const result = await callTool({
        name: 'get_logs',
        arguments: {
          project_id: project.id,
          service,
        },
      });

      expect(result).toEqual([]);
    }
  });

  test('get security advisors', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'get_advisors',
      arguments: {
        project_id: project.id,
        type: 'security',
      },
    });

    expect(result).toEqual({ lints: [] });
  });

  test('get performance advisors', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'get_advisors',
      arguments: {
        project_id: project.id,
        type: 'performance',
      },
    });

    expect(result).toEqual({ lints: [] });
  });

  test('get logs for invalid service type', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const invalidService = 'invalid-service';
    const getLogsPromise = callTool({
      name: 'get_logs',
      arguments: {
        project_id: project.id,
        service: invalidService,
      },
    });
    await expect(getLogsPromise).rejects.toThrow('Invalid enum value');
  });

  test('list edge functions', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const indexContent = codeBlock`
      Deno.serve(async (req: Request) => {
        return new Response('Hello world!', { headers: { 'Content-Type': 'text/plain' } })
      });
    `;

    const edgeFunction = await project.deployEdgeFunction(
      {
        name: 'hello-world',
        entrypoint_path: 'index.ts',
      },
      [
        new File([indexContent], 'index.ts', {
          type: 'application/typescript',
        }),
      ]
    );

    const result = await callTool({
      name: 'list_edge_functions',
      arguments: {
        project_id: project.id,
      },
    });

    expect(result).toEqual([
      {
        id: edgeFunction.id,
        slug: edgeFunction.slug,
        version: edgeFunction.version,
        name: edgeFunction.name,
        status: edgeFunction.status,
        entrypoint_path: 'index.ts',
        import_map_path: undefined,
        import_map: false,
        verify_jwt: true,
        created_at: expect.stringMatching(
          /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
        ),
        updated_at: expect.stringMatching(
          /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
        ),
      },
    ]);
  });

  test('get edge function', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const indexContent = codeBlock`
      Deno.serve(async (req: Request) => {
        return new Response('Hello world!', { headers: { 'Content-Type': 'text/plain' } })
      });
    `;

    const edgeFunction = await project.deployEdgeFunction(
      {
        name: 'hello-world',
        entrypoint_path: 'index.ts',
      },
      [
        new File([indexContent], 'index.ts', {
          type: 'application/typescript',
        }),
      ]
    );

    const result = await callTool({
      name: 'get_edge_function',
      arguments: {
        project_id: project.id,
        function_slug: edgeFunction.slug,
      },
    });

    expect(result).toEqual({
      id: edgeFunction.id,
      slug: edgeFunction.slug,
      version: edgeFunction.version,
      name: edgeFunction.name,
      status: edgeFunction.status,
      entrypoint_path: 'index.ts',
      import_map_path: undefined,
      import_map: false,
      verify_jwt: true,
      created_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
      updated_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
      files: [
        {
          name: 'index.ts',
          content: indexContent,
        },
      ],
    });
  });

  test('deploy new edge function', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const functionName = 'hello-world';
    const functionCode = 'console.log("Hello, world!");';

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        files: [
          {
            name: 'index.ts',
            content: functionCode,
          },
        ],
      },
    });

    expect(result).toEqual({
      id: expect.stringMatching(/^.+$/),
      slug: functionName,
      version: 1,
      name: functionName,
      status: 'ACTIVE',
      entrypoint_path: expect.stringMatching(/index\.ts$/),
      import_map_path: undefined,
      import_map: false,
      verify_jwt: true,
      created_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
      updated_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
    });
  });

  test('deploy edge function in read-only mode throws an error', async () => {
    const { callTool } = await setup({ readOnly: true });

    const org = await createOrganization({
      name: 'test-org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'test-app',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const functionName = 'hello-world';
    const functionCode = 'console.log("Hello, world!");';

    const result = callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        files: [
          {
            name: 'index.ts',
            content: functionCode,
          },
        ],
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot deploy an edge function in read-only mode.'
    );
  });

  test('deploy new version of existing edge function', async () => {
    const { callTool } = await setup();
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const functionName = 'hello-world';

    const edgeFunction = await project.deployEdgeFunction(
      {
        name: functionName,
        entrypoint_path: 'index.ts',
      },
      [
        new File(['console.log("Hello, world!");'], 'index.ts', {
          type: 'application/typescript',
        }),
      ]
    );

    expect(edgeFunction.version).toEqual(1);

    const originalCreatedAt = edgeFunction.created_at.getTime();
    const originalUpdatedAt = edgeFunction.updated_at.getTime();

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        files: [
          {
            name: 'index.ts',
            content: 'console.log("Hello, world! v2");',
          },
        ],
      },
    });

    expect(result).toEqual({
      id: edgeFunction.id,
      slug: functionName,
      version: 2,
      name: functionName,
      status: 'ACTIVE',
      entrypoint_path: expect.stringMatching(/index\.ts$/),
      import_map_path: undefined,
      import_map: false,
      verify_jwt: true,
      created_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
      updated_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
    });

    expect(new Date(result.created_at).getTime()).toEqual(originalCreatedAt);
    expect(new Date(result.updated_at).getTime()).toBeGreaterThan(
      originalUpdatedAt
    );
  });

  test('custom edge function import map', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const functionName = 'hello-world';
    const functionCode = 'console.log("Hello, world!");';

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        import_map_path: 'custom-map.json',
        files: [
          {
            name: 'index.ts',
            content: functionCode,
          },
          {
            name: 'custom-map.json',
            content: '{}',
          },
        ],
      },
    });

    expect(result.import_map).toBe(true);
    expect(result.import_map_path).toMatch(/custom-map\.json$/);
  });

  test('default edge function import map to deno.json', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const functionName = 'hello-world';
    const functionCode = 'console.log("Hello, world!");';

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        files: [
          {
            name: 'index.ts',
            content: functionCode,
          },
          {
            name: 'deno.json',
            content: '{}',
          },
        ],
      },
    });

    expect(result.import_map).toBe(true);
    expect(result.import_map_path).toMatch(/deno\.json$/);
  });

  test('default edge function import map to import_map.json', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const functionName = 'hello-world';
    const functionCode = 'console.log("Hello, world!");';

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        files: [
          {
            name: 'index.ts',
            content: functionCode,
          },
          {
            name: 'import_map.json',
            content: '{}',
          },
        ],
      },
    });

    expect(result.import_map).toBe(true);
    expect(result.import_map_path).toMatch(/import_map\.json$/);
  });

  test('updating edge function with missing import_map_path defaults to previous value', async () => {
    const { callTool } = await setup();
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const functionName = 'hello-world';

    const edgeFunction = await project.deployEdgeFunction(
      {
        name: functionName,
        entrypoint_path: 'index.ts',
        import_map_path: 'custom-map.json',
      },
      [
        new File(['console.log("Hello, world!");'], 'index.ts', {
          type: 'application/typescript',
        }),
        new File(['{}'], 'custom-map.json', {
          type: 'application/json',
        }),
      ]
    );

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        files: [
          {
            name: 'index.ts',
            content: 'console.log("Hello, world! v2");',
          },
          {
            name: 'custom-map.json',
            content: '{}',
          },
        ],
      },
    });

    expect(result.import_map).toBe(true);
    expect(result.import_map_path).toMatch(/custom-map\.json$/);
  });

  test('deploy edge function with verify_jwt disabled', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const functionName = 'webhook-handler';
    const functionCode = 'console.log("Webhook handler");';

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        verify_jwt: false,
        files: [
          {
            name: 'index.ts',
            content: functionCode,
          },
        ],
      },
    });

    expect(result).toEqual({
      id: expect.stringMatching(/^.+$/),
      slug: functionName,
      version: 1,
      name: functionName,
      status: 'ACTIVE',
      entrypoint_path: expect.stringMatching(/index\.ts$/),
      import_map_path: undefined,
      import_map: false,
      verify_jwt: false,
      created_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
      updated_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
    });
  });

  test('deploy edge function with verify_jwt enabled (default)', async () => {
    const { callTool } = await setup();

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const functionName = 'authenticated-function';
    const functionCode = 'console.log("Authenticated function");';

    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        files: [
          {
            name: 'index.ts',
            content: functionCode,
          },
        ],
      },
    });

    expect(result.verify_jwt).toBe(true);
  });

  test('update edge function verify_jwt from true to false', async () => {
    const { callTool } = await setup();
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const functionName = 'my-function';

    // First deploy with verify_jwt: true (default)
    const edgeFunction = await project.deployEdgeFunction(
      {
        name: functionName,
        entrypoint_path: 'index.ts',
        verify_jwt: true,
      },
      [
        new File(['console.log("v1");'], 'index.ts', {
          type: 'application/typescript',
        }),
      ]
    );

    expect(edgeFunction.verify_jwt).toBe(true);

    // Update with verify_jwt: false
    const result = await callTool({
      name: 'deploy_edge_function',
      arguments: {
        project_id: project.id,
        name: functionName,
        verify_jwt: false,
        files: [
          {
            name: 'index.ts',
            content: 'console.log("v2");',
          },
        ],
      },
    });

    expect(result.verify_jwt).toBe(false);
    expect(result.version).toBe(2);
  });

  test('create branch', async () => {
    const { callTool } = await setup({
      features: ['account', 'branching'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'branch',
        recurrence: 'hourly',
        amount: BRANCH_COST_HOURLY,
      },
    });

    const branchName = 'test-branch';
    const result = await callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: branchName,
        confirm_cost_id,
      },
    });

    expect(result).toEqual({
      id: expect.stringMatching(/^.+$/),
      name: branchName,
      project_ref: expect.stringMatching(/^.+$/),
      parent_project_ref: project.id,
      is_default: false,
      persistent: false,
      status: 'CREATING_PROJECT',
      created_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
      updated_at: expect.stringMatching(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/
      ),
    });
  });

  test('create branch in read-only mode throws an error', async () => {
    const { callTool } = await setup({
      readOnly: true,
      features: ['account', 'branching'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'branch',
        recurrence: 'hourly',
        amount: BRANCH_COST_HOURLY,
      },
    });

    const branchName = 'test-branch';
    const result = callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: branchName,
        confirm_cost_id,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot create a branch in read-only mode.'
    );
  });

  test('create branch without cost confirmation fails', async () => {
    const { callTool } = await setup({ features: ['branching'] });

    const org = await createOrganization({
      name: 'Paid Org',
      plan: 'pro',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const branchName = 'test-branch';
    const createBranchPromise = callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: branchName,
      },
    });

    await expect(createBranchPromise).rejects.toThrow(
      'User must confirm understanding of costs before creating a branch.'
    );
  });

  test('delete branch', async () => {
    const { callTool } = await setup({
      features: ['account', 'branching'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'branch',
        recurrence: 'hourly',
        amount: BRANCH_COST_HOURLY,
      },
    });

    const branch = await callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: 'test-branch',
        confirm_cost_id,
      },
    });

    const listBranchesResult = await callTool({
      name: 'list_branches',
      arguments: {
        project_id: project.id,
      },
    });

    expect(listBranchesResult).toContainEqual(
      expect.objectContaining({ id: branch.id })
    );
    expect(listBranchesResult).toHaveLength(2);

    await callTool({
      name: 'delete_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    const listBranchesResultAfterDelete = await callTool({
      name: 'list_branches',
      arguments: {
        project_id: project.id,
      },
    });

    expect(listBranchesResultAfterDelete).not.toContainEqual(
      expect.objectContaining({ id: branch.id })
    );
    expect(listBranchesResultAfterDelete).toHaveLength(1);

    const mainBranch = listBranchesResultAfterDelete[0];

    const deleteBranchPromise = callTool({
      name: 'delete_branch',
      arguments: {
        branch_id: mainBranch.id,
      },
    });

    await expect(deleteBranchPromise).rejects.toThrow(
      'Cannot delete the default branch.'
    );
  });

  test('delete branch in read-only mode throws an error', async () => {
    const { callTool } = await setup({
      readOnly: true,
      features: ['account', 'branching'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const branch = await createBranch({
      name: 'test-branch',
      parent_project_ref: project.id,
    });

    const listBranchesResult = await callTool({
      name: 'list_branches',
      arguments: {
        project_id: project.id,
      },
    });

    expect(listBranchesResult).toHaveLength(1);
    expect(listBranchesResult).toContainEqual(
      expect.objectContaining({ id: branch.id })
    );

    const result = callTool({
      name: 'delete_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot delete a branch in read-only mode.'
    );
  });

  test('list branches', async () => {
    const { callTool } = await setup({ features: ['branching'] });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const result = await callTool({
      name: 'list_branches',
      arguments: {
        project_id: project.id,
      },
    });

    expect(result).toStrictEqual([]);
  });

  test('merge branch', async () => {
    const { callTool } = await setup({
      features: ['account', 'branching', 'database'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'branch',
        recurrence: 'hourly',
        amount: BRANCH_COST_HOURLY,
      },
    });

    const branch = await callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: 'test-branch',
        confirm_cost_id,
      },
    });

    const migrationName = 'sample_migration';
    const migrationQuery =
      'create table sample (id integer generated always as identity primary key)';
    await callTool({
      name: 'apply_migration',
      arguments: {
        project_id: branch.project_ref,
        name: migrationName,
        query: migrationQuery,
      },
    });

    await callTool({
      name: 'merge_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    // Check that the migration was applied to the parent project
    const listResult = await callTool({
      name: 'list_migrations',
      arguments: {
        project_id: project.id,
      },
    });

    expect(listResult).toContainEqual({
      name: migrationName,
      version: expect.stringMatching(/^\d{14}$/),
    });
  });

  test('merge branch in read-only mode throws an error', async () => {
    const { callTool } = await setup({
      readOnly: true,
      features: ['account', 'branching', 'database'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const branch = await createBranch({
      name: 'test-branch',
      parent_project_ref: project.id,
    });

    const result = callTool({
      name: 'merge_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot merge a branch in read-only mode.'
    );
  });

  test('reset branch', async () => {
    const { callTool } = await setup({
      features: ['account', 'branching', 'database'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'branch',
        recurrence: 'hourly',
        amount: BRANCH_COST_HOURLY,
      },
    });

    const branch = await callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: 'test-branch',
        confirm_cost_id,
      },
    });

    // Create a table via execute_sql so that it is untracked
    const query =
      'create table test_untracked (id integer generated always as identity primary key)';
    await callTool({
      name: 'execute_sql',
      arguments: {
        project_id: branch.project_ref,
        query,
      },
    });

    const firstTablesResult = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: branch.project_ref,
      },
    });

    expect(firstTablesResult).toContainEqual(
      expect.objectContaining({ name: 'test_untracked' })
    );

    await callTool({
      name: 'reset_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    const secondTablesResult = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: branch.project_ref,
      },
    });

    // Expect the untracked table to be removed after reset
    expect(secondTablesResult).not.toContainEqual(
      expect.objectContaining({ name: 'test_untracked' })
    );
  });

  test('reset branch in read-only mode throws an error', async () => {
    const { callTool } = await setup({
      readOnly: true,
      features: ['account', 'branching', 'database'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const branch = await createBranch({
      name: 'test-branch',
      parent_project_ref: project.id,
    });

    const result = callTool({
      name: 'reset_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot reset a branch in read-only mode.'
    );
  });

  test('revert migrations', async () => {
    const { callTool } = await setup({
      features: ['account', 'branching', 'database'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'branch',
        recurrence: 'hourly',
        amount: BRANCH_COST_HOURLY,
      },
    });

    const branch = await callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: 'test-branch',
        confirm_cost_id,
      },
    });

    const migrationName = 'sample_migration';
    const migrationQuery =
      'create table sample (id integer generated always as identity primary key)';
    await callTool({
      name: 'apply_migration',
      arguments: {
        project_id: branch.project_ref,
        name: migrationName,
        query: migrationQuery,
      },
    });

    // Check that migration has been applied to the branch
    const firstListResult = await callTool({
      name: 'list_migrations',
      arguments: {
        project_id: branch.project_ref,
      },
    });

    expect(firstListResult).toContainEqual({
      name: migrationName,
      version: expect.stringMatching(/^\d{14}$/),
    });

    const firstTablesResult = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: branch.project_ref,
      },
    });

    expect(firstTablesResult).toContainEqual(
      expect.objectContaining({ name: 'sample' })
    );

    await callTool({
      name: 'reset_branch',
      arguments: {
        branch_id: branch.id,
        migration_version: '0',
      },
    });

    // Check that all migrations have been reverted
    const secondListResult = await callTool({
      name: 'list_migrations',
      arguments: {
        project_id: branch.project_ref,
      },
    });

    expect(secondListResult).toStrictEqual([]);

    const secondTablesResult = await callTool({
      name: 'list_tables',
      arguments: {
        project_id: branch.project_ref,
      },
    });

    expect(secondTablesResult).not.toContainEqual(
      expect.objectContaining({ name: 'sample' })
    );
  });

  test('rebase branch', async () => {
    const { callTool } = await setup({
      features: ['account', 'branching', 'database'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const confirm_cost_id = await callTool({
      name: 'confirm_cost',
      arguments: {
        type: 'branch',
        recurrence: 'hourly',
        amount: BRANCH_COST_HOURLY,
      },
    });

    const branch = await callTool({
      name: 'create_branch',
      arguments: {
        project_id: project.id,
        name: 'test-branch',
        confirm_cost_id,
      },
    });

    const migrationName = 'sample_migration';
    const migrationQuery =
      'create table sample (id integer generated always as identity primary key)';
    await callTool({
      name: 'apply_migration',
      arguments: {
        project_id: project.id,
        name: migrationName,
        query: migrationQuery,
      },
    });

    await callTool({
      name: 'rebase_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    // Check that the production migration was applied to the branch
    const listResult = await callTool({
      name: 'list_migrations',
      arguments: {
        project_id: branch.project_ref,
      },
    });

    expect(listResult).toContainEqual({
      name: migrationName,
      version: expect.stringMatching(/^\d{14}$/),
    });
  });

  test('rebase branch in read-only mode throws an error', async () => {
    const { callTool } = await setup({
      readOnly: true,
      features: ['account', 'branching', 'database'],
    });

    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const branch = await createBranch({
      name: 'test-branch',
      parent_project_ref: project.id,
    });

    const result = callTool({
      name: 'rebase_branch',
      arguments: {
        branch_id: branch.id,
      },
    });

    await expect(result).rejects.toThrow(
      'Cannot rebase a branch in read-only mode.'
    );
  });

  // We use snake_case because it aligns better with most MCP clients
  test('all tools follow snake_case naming convention', async () => {
    const { client } = await setup();

    const { tools } = await client.listTools();

    for (const tool of tools) {
      expect(tool.name, 'expected tool name to be snake_case').toMatch(
        /^[a-z0-9_]+$/
      );

      const parameterNames = Object.keys(tool.inputSchema.properties ?? {});
      for (const name of parameterNames) {
        expect(name, 'expected parameter to be snake_case').toMatch(
          /^[a-z0-9_]+$/
        );
      }
    }
  });

  test('all tools provide annotations', async () => {
    const { client } = await setup();

    const { tools } = await client.listTools();

    for (const tool of tools) {
      expect(tool.annotations, `${tool.name} tool`).toBeDefined();
      expect(tool.annotations!.title, `${tool.name} tool`).toBeDefined();
      expect(tool.annotations!.readOnlyHint, `${tool.name} tool`).toBeDefined();
      expect(
        tool.annotations!.destructiveHint,
        `${tool.name} tool`
      ).toBeDefined();
      expect(
        tool.annotations!.idempotentHint,
        `${tool.name} tool`
      ).toBeDefined();
      expect(
        tool.annotations!.openWorldHint,
        `${tool.name} tool`
      ).toBeDefined();
    }
  });
});

describe('feature groups', () => {
  test('account tools', async () => {
    const { client } = await setup({
      features: ['account'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'list_organizations',
      'get_organization',
      'list_projects',
      'get_project',
      'get_cost',
      'confirm_cost',
      'create_project',
      'pause_project',
      'restore_project',
    ]);
  });

  test('database tools', async () => {
    const { client } = await setup({
      features: ['database'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'list_tables',
      'list_extensions',
      'list_migrations',
      'apply_migration',
      'execute_sql',
    ]);
  });

  test('debugging tools', async () => {
    const { client } = await setup({
      features: ['debugging'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual(['get_logs', 'get_advisors']);
  });

  test('development tools', async () => {
    const { client } = await setup({
      features: ['development'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'get_project_url',
      'get_publishable_keys',
      'generate_typescript_types',
    ]);
  });

  test('docs tools', async () => {
    const { client } = await setup({
      features: ['docs'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual(['search_docs']);
  });

  test('functions tools', async () => {
    const { client } = await setup({
      features: ['functions'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'list_edge_functions',
      'get_edge_function',
      'deploy_edge_function',
    ]);
  });

  test('branching tools', async () => {
    const { client } = await setup({
      features: ['branching'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'create_branch',
      'list_branches',
      'delete_branch',
      'merge_branch',
      'reset_branch',
      'rebase_branch',
    ]);
  });

  test('storage tools', async () => {
    const { client } = await setup({
      features: ['storage'],
    });

    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'list_storage_buckets',
      'get_storage_config',
      'update_storage_config',
    ]);
  });

  test('invalid group fails', async () => {
    const setupPromise = setup({
      features: ['my-invalid-group'],
    });

    await expect(setupPromise).rejects.toThrow('Invalid enum value');
  });

  test('duplicate group behaves like single group', async () => {
    const { client: duplicateClient } = await setup({
      features: ['account', 'account'],
    });

    const { tools } = await duplicateClient.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'list_organizations',
      'get_organization',
      'list_projects',
      'get_project',
      'get_cost',
      'confirm_cost',
      'create_project',
      'pause_project',
      'restore_project',
    ]);
  });

  test('tools filtered to available platform operations', async () => {
    const platform: SupabasePlatform = {
      database: {
        executeSql() {
          throw new Error('Not implemented');
        },
        listMigrations() {
          throw new Error('Not implemented');
        },
        applyMigration() {
          throw new Error('Not implemented');
        },
      },
    };

    const { client } = await setup({ platform });
    const { tools } = await client.listTools();
    const toolNames = tools.map((tool) => tool.name);

    expect(toolNames).toEqual([
      'search_docs',
      'list_tables',
      'list_extensions',
      'list_migrations',
      'apply_migration',
      'execute_sql',
    ]);
  });

  test('unimplemented feature group produces custom error message', async () => {
    const platform: SupabasePlatform = {
      database: {
        executeSql() {
          throw new Error('Not implemented');
        },
        listMigrations() {
          throw new Error('Not implemented');
        },
        applyMigration() {
          throw new Error('Not implemented');
        },
      },
    };

    const setupPromise = setup({ platform, features: ['account'] });

    await expect(setupPromise).rejects.toThrow(
      "This platform does not support the 'account' feature group"
    );
  });
});

describe('project scoped tools', () => {
  test('no account level tools should exist', async () => {
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const { client } = await setup({ projectId: project.id });

    const result = await client.listTools();

    const accountLevelToolNames = [
      'list_organizations',
      'get_organization',
      'list_projects',
      'get_project',
      'get_cost',
      'confirm_cost',
      'create_project',
      'pause_project',
      'restore_project',
    ];

    const toolNames = result.tools.map((tool) => tool.name);

    for (const accountLevelToolName of accountLevelToolNames) {
      expect(
        toolNames,
        `tool ${accountLevelToolName} should not be available in project scope`
      ).not.toContain(accountLevelToolName);
    }
  });

  test('no tool should accept a project_id', async () => {
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });

    const { client } = await setup({ projectId: project.id });

    const result = await client.listTools();

    expect(result.tools).toBeDefined();
    expect(Array.isArray(result.tools)).toBe(true);

    for (const tool of result.tools) {
      const schemaProperties = tool.inputSchema.properties ?? {};
      expect(
        'project_id' in schemaProperties,
        `tool ${tool.name} should not accept a project_id`
      ).toBe(false);
    }
  });

  test('invalid project ID should throw an error', async () => {
    const { callTool } = await setup({ projectId: 'invalid-project-id' });

    const listTablesPromise = callTool({
      name: 'list_tables',
      arguments: {
        schemas: ['public'],
      },
    });

    await expect(listTablesPromise).rejects.toThrow('Project not found');
  });

  test('passing project_id to a tool should throw an error', async () => {
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    const { callTool } = await setup({ projectId: project.id });

    const listTablesPromise = callTool({
      name: 'list_tables',
      arguments: {
        project_id: 'my-project-id',
        schemas: ['public'],
      },
    });

    await expect(listTablesPromise).rejects.toThrow('Unrecognized key');
  });

  test('listing tables implicitly uses the scoped project_id', async () => {
    const org = await createOrganization({
      name: 'My Org',
      plan: 'free',
      allowed_release_channels: ['ga'],
    });

    const project = await createProject({
      name: 'Project 1',
      region: 'us-east-1',
      organization_id: org.id,
    });
    project.status = 'ACTIVE_HEALTHY';

    project.db
      .sql`create table test (id integer generated always as identity primary key)`;

    const { callTool } = await setup({ projectId: project.id });

    const result = await callTool({
      name: 'list_tables',
      arguments: {
        schemas: ['public'],
      },
    });

    expect(result).toEqual([
      expect.objectContaining({
        name: 'test',
        schema: 'public',
        columns: [
          expect.objectContaining({
            name: 'id',
            options: expect.arrayContaining(['identity']),
          }),
        ],
      }),
    ]);
  });
});

describe('docs tools', () => {
  test('gets content', async () => {
    const { callTool } = await setup();
    const query = stripIndent`
      query ContentQuery {
        searchDocs(query: "typescript") {
          nodes {
            title
            href
          }
        }
      }
    `;

    const result = await callTool({
      name: 'search_docs',
      arguments: {
        graphql_query: query,
      },
    });

    expect(result).toEqual({ dummy: true });
  });

  test('tool description contains schema', async () => {
    const { client } = await setup();

    const { tools } = await client.listTools();

    const tool = tools.find((tool) => tool.name === 'search_docs');

    if (!tool) {
      throw new Error('tool not found');
    }

    if (!tool.description) {
      throw new Error('tool description not found');
    }

    const minifiedSchema = gqlmin(contentApiMockSchema);
    expect(tool.description.includes(minifiedSchema)).toBe(true);
  });

  test('schema is only loaded when listing tools', async () => {
    const { client, callTool } = await setup();

    expect(mockContentApiSchemaLoadCount.value).toBe(0);

    // "tools/list" requests fetch the schema
    await client.listTools();
    expect(mockContentApiSchemaLoadCount.value).toBe(1);

    // "tools/call" should not fetch the schema again
    await callTool({
      name: 'search_docs',
      arguments: {
        graphql_query: '{ searchDocs(query: "test") { nodes { title } } }',
      },
    });
    expect(mockContentApiSchemaLoadCount.value).toBe(1);

    // Additional "tools/list" requests fetch the schema again
    await client.listTools();
    expect(mockContentApiSchemaLoadCount.value).toBe(2);
  });
});
